package be.ac.ulb.mlg.utils.discretizer;

import be.ac.ulb.mlg.utils.Discretizer;

/*
 * The MIT License (MIT)
 * 
 * Copyright (c) 2013 Jean-Sebastien Lerat (Jean-Sebastien.Lerat@ulb.ac.be)
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/**
 * 
 * @author Jean-Sebastien Lerat (Jean-Sebastien.Lerat@ulb.ac.be)
 * @version 1.00, 30/08/2013
 */

/**
 * The UniformWidthDiscretizer 
 */
public class UniformWidthDiscretizer implements Discretizer{
	private final Mode mode;
	private final double range;
	private double min,max;
	private final boolean preprocess;
	/**
	 * Constructor of UniformDiscretizer which setup the mode and the range of discretization without preprocessing
	 * @param mode The mode of discretization column/row wise or by using the whole matrix
	 * @param range The number of value between min/max values of each row/column
	 */
	public UniformWidthDiscretizer(Mode mode,int range){
		this(mode,range,false);
	}
	/**
	 * Constructor of UniformDiscretizer which setup the mode and the range of discretization
	 * @param mode The mode of discretization column/row wise or by using the whole matrix
	 * @param range The number of value between min/max values of each row/column
	 * @param specify if the method has to preprocess (compute measure before normalization, required for GLOBAL {@see Mode})
	 */
	public UniformWidthDiscretizer(Mode mode,int range,final boolean preprocess){
		this.mode	= mode;
		this.range	= range;
		this.preprocess = preprocess;
	}
	@Override
	public double[][] discretize(double[][] matrix){
		final double[][] result = new double[matrix.length][];

		for(int i=0;i<matrix.length;i++)
			result[i] = new double[matrix[i].length];
		
		if(mode.equals(Mode.ROW_WISE))
			rowWise(matrix,result);
		else if(mode.equals(Mode.COLUMN_WISE))
			columnWise(matrix,result);
		else {
			if(!preprocess) preprocess(matrix);
			int i,j;
			for(i=0;i<matrix.length;i++)
				for(j=0;j<matrix[i].length;j++)
					result[i][j] = rangeIndex(matrix[i][j]-min, max);
		}
		return result;
	}
	private void columnWise(double[][] matrix,double[][] result) {//need to have the same number of columns
		int row,column;
		double min,max;
		for(column=0;column<result[0].length;column++){
			min = Double.MAX_VALUE;
			max = Double.MIN_VALUE;
			for(row=0;row<result.length;row++)
				if(!Double.isNaN(matrix[row][column])){
					min = Math.min(min, matrix[row][column]);
					max = Math.max(max, matrix[row][column]);
				}
			max -= min;
			max /= range;
			for(row=0;row<result.length;row++)
				result[row][column] = rangeIndex(matrix[row][column]-min, max);
		}
	}
	private void rowWise(double[][] matrix,double[][] result) {
		int row,column;
		double min,max;
		for(row=0;row<result.length;row++){
			min = Double.MAX_VALUE;
			max = Double.MIN_VALUE;
			for(column=0;column<result[row].length;column++)
				if(!Double.isNaN(matrix[row][column])){
					min = Math.min(min, matrix[row][column]);
					max = Math.max(max, matrix[row][column]);
				}
			max -= min;
			max /= range;
			for(column=0;column<result[row].length;column++)
				result[row][column] = rangeIndex(matrix[row][column]-min, max);
		}
	}
	private double rangeIndex(double value,final double step){
		if(Double.isNaN(value)) return Double.NaN;
		value = (double)(((int)(value/step))+1);
		return value >= range ? range: value;
	}
	@Override
	public boolean hasNativeImplementation() {
		return false;//TODO implement it
	}
	@Override
	public boolean requirePreprocessing() {
		return preprocess;
	}
	@Override
	public void preprocess(double[][] matrix) {
		if(!mode.equals(Mode.GLOBAL))return;
		int i,j;
		min = Double.MAX_VALUE;
		max = Double.MIN_VALUE;
		for(i=0;i<matrix.length;i++)
			for(j=0;j<matrix[i].length;j++)
				if(!Double.isNaN(matrix[i][j])){
					min = Math.min(min, matrix[i][j]);
					max = Math.max(max, matrix[i][j]);
				}
		max -= min;
		max /= range;
	}
}
